package com.guodi.common.utils;

import com.guodi.core.minio.util.MinioUtil;
import org.apache.any23.encoding.TikaEncodingDetector;
import org.apache.commons.lang.StringUtils;
import org.springframework.core.io.ClassPathResource;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URLEncoder;
import java.nio.charset.Charset;
import java.util.*;
import java.util.stream.Collectors;

/**
* @描述: 文件操作工具类
* @作者: 代鹏刚
* @日期: 2022/4/26 15:38
**/
public class FileUtil {

    /**
     * @描述: 创建文件夹
     * @入参:
     * @出参:
     * @作者: 余家鑫
     * @日期: 2021/3/19 上午10:13
     */
    public static File mkdir(File folder) {
        if (Objects.nonNull(folder) && !folder.exists()) {
            folder.mkdirs();
        }
        return folder;
    }

    /**
     * @描述: 创建父类文件目录
     * @入参:
     * @出参:
     * @作者: 余家鑫
     * @日期: 2021/3/19 上午10:09
     */
    public static File mkParentDir(File file) {
        File parentFile = file.getParentFile();
        if (Objects.nonNull(parentFile) && !parentFile.exists()) {
            mkdir(parentFile);
        }
        return parentFile;
    }

    /**
     * @描述: 获取某个文件的父目录名字
     * @入参:
     * @出参:
     * @作者: 余家鑫
     * @日期: 2021/3/19 下午2:53
     */
    public static String getParentName(File file) {
        if(Objects.isNull(file) || !file.exists()){
            return "";
        }
        File parentFile = file.getParentFile();
        if(Objects.isNull(parentFile) || !parentFile.exists()){
            return "";
        }
        return parentFile.getName();
    }

    /**
     * @描述: 创建文件及其父目录，如果这个文件存在，直接返回这个文件
     * @入参: file 目标文件
     * @出参:
     * @作者: 余家鑫
     * @日期: 2021/3/19 上午10:07
     */
    public static File touch(File file) {
        if (Objects.isNull(file)) {
            return null;
        }
        if (!file.exists()) {
            mkParentDir(file);
            try {
                file.createNewFile();
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
        return file;
    }

    /**
     * @描述: 创建File对象
     * @入参: parent 父路径
     * @出参: path 父路径后的子路径
     * @作者: 余家鑫
     * @日期: 2021/3/19 上午9:59
     */
    public static File file(File parent, String path) {
        if (StringUtils.isBlank(path)) {
            throw new NullPointerException("子路径为空");
        }
        // 校验子目录在父目录里面
        return checkChildInParent(parent, new File(parent, path));
    }

    /**
     * @描述: 根据path获取File对象
     * @入参:
     * @出参:
     * @作者: 余家鑫
     * @日期: 2021/3/19 下午2:14
     */
    public static File file(String path) {
        if (StringUtils.isBlank(path)) {
            throw new NullPointerException("路径为空");
        }
        // 校验子目录在父目录里面
        return new File(path);
    }

    /**
     * @描述: 校验子目录在父目录中
     * @入参: parentFile 父目录
     * @入参: file 子目录
     * @出参:
     * @作者: 余家鑫
     * @日期: 2021/3/19 上午10:02
     */
    public static File checkChildInParent(File parentFile, File file) {
        if (Objects.nonNull(parentFile) && Objects.nonNull(file)) {
            String parentCanonicalPath;
            String canonicalPath;
            try {
                parentCanonicalPath = parentFile.getCanonicalPath();
                canonicalPath = file.getCanonicalPath();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
            if (!canonicalPath.startsWith(parentCanonicalPath)) {
                throw new IllegalArgumentException("子目录不在父目录中: " + file.getName());
            }
        }
        return file;
    }

    /**
     * @描述: 将流的内容写入文件
     * @入参: in 输入流
     * @入参: targetFile 输出到的目标文件
     * @出参:
     * @作者: 余家鑫
     * @日期: 2021/3/19 上午10:03
     */
    public static File writeFromStream(InputStream in, File targetFile) {
        // 文件则写入具体的路径中
        FileOutputStream out = null;
        try {
            out = new FileOutputStream(FileUtil.touch(targetFile));
            BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(out);
            int n;
            byte[] bytes = new byte[1024];
            while ((n = in.read(bytes)) != -1) {
                bufferedOutputStream.write(bytes, 0, n);
            }
            //关闭流
            bufferedOutputStream.close();
            out.close();
        }catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            //IOUtils.closeQuietly(out);
            if (out != null) {
                try {
                    out.close();
                } catch (final IOException ignored) {
                }
            }
        }
        return targetFile;
    }

    /**
     * @描述: 获取文件目录树
     * @入参:
     * @出参:
     * @作者: 余家鑫
     * @日期: 2021/3/19 下午2:28
     */
    public static List<FileNodeDTO> tree(File folder) {
        if(Objects.isNull(folder) || !folder.exists()){
            return Collections.emptyList();
        }
        if(folder.isFile()){
            return Collections.emptyList();
        }
        List<FileNodeDTO> list = new ArrayList<>();
        fillFileNodes(folder, list, folder.getName() + "/");
        return list;
    }

    /**
     * @描述: 读取输入流，返回每行内容
     * @入参: inputStream 输入流，内容必须是文本形式，不能是字节形式，反正乱码
     * @出参: List<String> 元素为每行的文本内容，编码为默认编码，可以通过启动jar包时指定编码：-Dfile.encoding=utf-8
     * @作者: 陈宏成
     * @日期: 2021-04-14 09:54
     */
    public static List<String> readLineStr(InputStream inputStream){
        InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
        BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
        List<String> collect = bufferedReader.lines().collect(Collectors.toList());
        return collect;
    }

    /**
     * @描述: 把文件目录, 填充到文件节点列表中
     * @入参:
     * @出参:
     * @作者: 余家鑫
     * @日期: 2021/3/19 下午3:05
     */
    private static void fillFileNodes(File folder, List<FileNodeDTO> fileNodes, String parentDir){
        File[] files = folder.listFiles();
        for(File file : files){
            String name = file.getName();
            boolean isDirectory = file.isDirectory();
            String relativePath = isDirectory ? (parentDir + name + "/") : (parentDir + name) ;
            FileNodeDTO dto = new FileNodeDTO();
            dto.setName(name);
            dto.setChildren(new ArrayList<>());
            dto.setIsFile(!isDirectory);
            dto.setRelativePath(relativePath);
            if(isDirectory){
                //若是目录，则递归该目录下的文件
                List<FileNodeDTO> children = dto.getChildren();
                fillFileNodes(file, children, relativePath);
            }
            fileNodes.add(dto);
        }
    }

    /**
     * @描述: 进行中文编码
     * @入参:
     * @出参:
     * @作者: 余家鑫
     * @日期: 2021/5/20 下午5:32
     */
    public static String filenameEncoding(String filename, HttpServletRequest request) throws IOException {
        String agent = request.getHeader("User-Agent"); //获取浏览器
        if (agent.contains("Firefox")) {
            filename = "=?utf-8?B?"
                    + Base64.getEncoder().encode(filename.getBytes("utf-8"))
                    + "?=";
        } else if (agent.contains("MSIE")) {
            filename = URLEncoder.encode(filename, "utf-8");
        } else if (agent.contains("Safari")) {
            filename = new String(filename.getBytes("utf-8"), "ISO8859-1");
        } else {
            filename = URLEncoder.encode(filename, "utf-8");
        }
        return filename;
    }

    /**
    * @描述: 递归删除文件目录
    * @入参:
    * @出参:
    * @作者: 代鹏刚
    * @日期: 2021/6/15 14:41
    **/
    public static void deleteDir(String dirPath) {
        File file = new File(dirPath);
        if (file.exists()) {
            File files[] = file.listFiles();
            int len = files.length;
            for (int i = 0; i < len; i++) {
                if (files[i].isDirectory()) {
                    deleteDir(files[i].getPath());
                } else {
                    files[i].delete();
                }
            }
            file.delete();
        }
    }

    /**
    * @描述: 验证一个文件目录下是否包含txt文件
    * @入参:
    * @出参:
    * @作者: 代鹏刚
    * @日期: 2021/6/15 16:15
    **/
    public static boolean isExistTXTFile(File file) {
        File filelist[] = file.listFiles();
        for (File f : filelist) {
            String filename = f.getName();
            if (filename.endsWith("txt")) {
                return true;
            }
        }
        return false;
    }

    /**
     * 上传到 Minio 文件服务器
     * @入参： url 文件网络url
     * @入参： objectName 对象名，也就是目录名
     * @入参： fileName 文件名，如果目录中已经有这个文件了，那就会覆盖
     * @入参： reNameRule 命名规则
     * @出参： 文件地址
     * @作者： 陈宏成
     * @日期： 2021/10/30
     */
    public static String uploadMinio(String url, MinioObjectNameEnum objectName, String fileName, ReNameRuleEnum reNameRule) throws Exception {
        HttpRequester httpRequester = new HttpRequester();
        HttpRespons httpRespons = (HttpRespons) httpRequester.sendGet(url);
        byte[] bytay = httpRespons.getBytay();
        ByteArrayInputStream inputStream = new ByteArrayInputStream(bytay);
        return  uploadMinio(inputStream ,  objectName,  fileName,  reNameRule);
    }

    /**
     * 上传到 Minio 文件服务器
     * @入参： file 文件
     * @入参： objectName 对象名，也就是目录名
     * @入参： fileName 文件名，如果目录中已经有这个文件了，那就会覆盖
     * @入参： reNameRule 命名规则
     * @出参： 文件地址
     * @作者： 陈宏成
     * @日期： 2021/10/30
     */
    public static String uploadMinio(File file, MinioObjectNameEnum objectName, String fileName, ReNameRuleEnum reNameRule) throws Exception {
        if (Objects.isNull(file) || !file.exists() || file.isDirectory()){
            throw new RuntimeException("文件不存在或者是目录");
        }
        FileInputStream fileInputStream = new FileInputStream(file);
        return  uploadMinio(fileInputStream ,  objectName,  fileName,  reNameRule);
    }

    /**
     * 上传到 Minio 文件服务器
     * @入参： bytay 文件字节
     * @入参： objectName 对象名，也就是目录名
     * @入参： fileName 文件名，如果目录中已经有这个文件了，那就会覆盖
     * @入参： reNameRule 命名规则
     * @出参： 文件地址
     * @作者： 陈宏成
     * @日期： 2021/10/30
     */
    public static String uploadMinio(byte[] bytay, MinioObjectNameEnum objectName, String fileName, ReNameRuleEnum reNameRule) throws Exception {
        ByteArrayInputStream inputStream = new ByteArrayInputStream(bytay);
        return  uploadMinio(inputStream ,  objectName,  fileName,  reNameRule);
    }

    /**
     * 上传到 Minio 文件服务器
     * @入参： multipartFile 文件对象
     * @入参： objectName 对象名，也就是目录名
     * @入参： fileName 文件名，如果目录中已经有这个文件了，那就会覆盖
     * @入参： reNameRule 命名规则
     * @出参： 文件地址
     * @作者： 陈宏成
     * @日期： 2021/10/30
     */
    public static String uploadMinio(MultipartFile multipartFile, MinioObjectNameEnum objectName, ReNameRuleEnum reNameRule) throws Exception {
        return  uploadMinio(multipartFile.getInputStream(),  objectName,  multipartFile.getOriginalFilename(),  reNameRule);
    }

    /**
     * 上传到 Minio 文件服务器
     * @入参： inputStream 文件流
     * @入参： objectName 对象名，也就是目录名
     * @入参： fileName 文件名，如果目录中已经有这个文件了，那就会覆盖
     * @入参： reNameRule 命名规则
     * @出参： 文件地址
     * @作者： 陈宏成
     * @日期： 2021/10/30
     */
    public static String uploadMinio(InputStream inputStream, MinioObjectNameEnum objectName, String fileName, ReNameRuleEnum reNameRule) throws Exception {
        if (inputStream == null){
            throw new RuntimeException("文件流不存在");
        }
        if (objectName == null){
            throw new RuntimeException("对象名不能为空");
        }
        if (reNameRule == null){
            throw new RuntimeException("命名规则不能为空");
        }
        if (reNameRule == ReNameRuleEnum.NO_RENAME && StringUtils.isBlank(fileName)){
            throw new RuntimeException("文件名不能为空");
        }
        // 重命名
        if (reNameRule == ReNameRuleEnum.TIMESTAMP){
            String timestamp = String.valueOf(System.nanoTime());
            if (fileName.contains(".")){
                fileName = timestamp + fileName.substring(fileName.lastIndexOf("."));
            }else{
                fileName = timestamp;
            }
        }else if (reNameRule == ReNameRuleEnum.NAME_TIMESTAMP){
            String timestamp = String.valueOf(System.currentTimeMillis());
            if (fileName.contains(".")){
                String name = fileName.substring(0, fileName.lastIndexOf("."));
                fileName = name + "-" + timestamp + fileName.substring(fileName.lastIndexOf("."));
            }else{
                fileName = fileName + "-" + timestamp;
            }
        }
        // 上传
        String file = MinioUtil.uploadFile(inputStream, objectName.getObjectName(), fileName);
        inputStream.close();
        return file;
    }

    /**
     * 删除超过一定时间的文件，如果是文件，则直接判断删除。如果是目录，则只判断删除这目录底下一级的文件，子目录不管
     * @入参： path 文件路径
     * @入参： daysAgo 多少天前
     */
    public static boolean deleteHistoryFile(String path, int daysAgo){
        File file = new File(path);
        if (StringUtils.isBlank(path)){
            return false;
        }
        if (!file.exists()){
            return false;
        }
        if (daysAgo <= 0){
            return false;
        }
        if (file.isFile()){
            // 文件直接判断删除
            long l = file.lastModified();
            long l1 = System.currentTimeMillis();
            if ((l1 - l) > (daysAgo * 24L * 60L * 60L * 1000L)) {
                return file.delete();
            }
        }else if (file.isDirectory()){
            // 目录则会对直接子级的文件进行操作
            long l1 = System.currentTimeMillis();
            File[] files = file.listFiles(new FileFilter() {
                @Override
                public boolean accept(File pathname) {
                    if (!pathname.isFile()){
                        return false;
                    }
                    long l = pathname.lastModified();
                    if ((l1 - l) > (daysAgo * 24L * 60L * 60L * 1000L)) {
                        return true;
                    }
                    return false;
                }
            });
            if (files != null && files.length > 0){
                boolean isOk = true;
                for(File file1 : files) {
                    isOk = isOk & file1.delete();
                }
                return isOk;
            }
        }
        return false;
    }

    public static enum ReNameRuleEnum{
        // 不重命名
        NO_RENAME,
        // 时间戳
        TIMESTAMP,
        // 原始名-时间戳
        NAME_TIMESTAMP;
    }

    public static enum MinioObjectNameEnum{
        /**
         * 富文本框
         */
        UEDITOR("ueditor"),
        /**
         * 图则制作
         */
        PLAN_INFO("plan_info"),
        /**
         * 成果审查
         */
        OC_OUTCOME("oc_outcome"),
        /**
         * 其他
         */
        OTHER("other");

        private String objectName;

        private MinioObjectNameEnum(String objectName){
            this.objectName = objectName;
        }

        public String getObjectName() {
            return objectName;
        }
    }

    /**
     * @描述:字符串转文件保存
     * @入参:
     * @出参:返回文件的绝对路径
     * @作者: 戴伙进
     * @日期: 2020/5/21 20:52
    **/
    public static String filesave(String str, String path) throws Exception {
        PrintWriter pw = null;
        FileOutputStream fos = null;
        try {
            File txt = new File(path);
            if (!txt.exists()) {
                txt.createNewFile();
            }
            fos = new FileOutputStream(txt);
            pw = new PrintWriter(fos);
            pw.write(str.toCharArray());
            return txt.getAbsolutePath();
        } catch (Exception e) {
            throw e;
        } finally {
            if (pw != null) {
                pw.close();
                pw.flush();
            }
            if (fos != null) {
                fos.close();
            }
        }
    }

    /**
     * @描述:删除空目录
     * @入参:@dir 将要删除的目录路径
     * @出参:
     * @作者: 戴伙进
     * @日期: 2020/5/22 8:56
    **/
    public static void doDeleteEmptyDir(String dir) throws IOException{
        File file = new File(dir);
        if (!file.exists()) {
            throw new IOException("找不到指定目录");
        }
        file.delete();
    }

    /**
     * @描述:递归删除目录下的所有文件及子目录下的所有文件
     * @入参:
     * @出参:
     * @作者: 戴伙进
     * @日期: 2020/5/22 9:00
    **/
    public static void deleteFileAll(File file){
       if(file.exists()){
           File files[] = file.listFiles();
           int len = files.length;
           for (int i = 0; i <len ; i++) {
               if(files[i].isDirectory()){
                   deleteFileAll(files[i]);
               }else {
                   files[i].delete();
               }
           }
           file.delete();
       }
    }

    /**
     * @描述:删除单个文件
     * @入参:@fileName 要删除的文件名
     * @出参: 单个文件删除成功返回true,否者返回false
     * @作者: 戴伙进
     * @日期: 2020/5/22 9:03
    **/
    public static boolean deleteFile(String fileName){
        File file = new File(fileName);
        //如果文件存在并且是一个文件，直接删除
        if(file.exists() && file.isFile()){
            return file.delete();
        }
        return  false;
    }

    /**
     * 判断文件是否存在
     *
     * @入参： path
     * @return
     */
    public static boolean exists(String path) {
        File file = new File(path);
        if (file.exists()) {
            return true;
        } else {
            return false;
        }
    }

    /**
     * @描述:获取文件下的所有文件名称
     * @入参:
     * @出参:
     * @作者: 戴伙进
     * @日期: 2020/5/21 18:26
     **/
    public static List<String> getDirFiles(String path){
        List<String> list = new ArrayList<>();
        File file = new File(path);
        //如果这个路径是文件夹
        if(file.isDirectory()){
            File[] files = file.listFiles();
            for (int i = 0; i <files.length ; i++) {
                list.add(files[i].getPath());
            }
        }
        return list;
    }

    /**
     * @描述:txt文本转换成字符串
     * @入参:
     * @出参:
     * @作者: 戴伙进
     * @日期: 2020/5/31 17:12
     **/
    public static String txt2String(InputStream inputStream,String charsetName){
        StringBuilder result = new StringBuilder();
        try{
            BufferedReader br = new BufferedReader(new InputStreamReader(inputStream,charsetName));
            String s = null;
            while ((s = br.readLine()) != null){
                result.append(System.lineSeparator()+s);
            }
            br.close();
        }catch (Exception ex){
            ex.printStackTrace();
        }
        return result.toString();
    }

    /**
     * @描述:获取项目的类路径
     * @入参:
     * @出参:
     * @作者: 戴伙进
     * @日期: 2020/6/2 17:07
    **/
    public static String getProClassPath (){
        String proClassPath="";
        try {
            org.springframework.core.io.Resource resource = new ClassPathResource("");
            proClassPath = resource.getFile().getAbsolutePath();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return proClassPath;
    }

    /**
     * @描述:将本地目录打包成zip进行下载
     * @入参:
     * @出参:
     * @作者: 戴伙进
     * @日期: 2020/6/6 18:28
     **/
    public static void downloadZipFile(String path, HttpServletResponse response, String downloadFileName) {
        OutputStream out = null;
        String downloadName;
        if(StringUtils.isNotBlank(downloadFileName)){
            downloadName =downloadFileName+".zip";
        }else {
            downloadName ="下载文件.zip";
        }
        try {
            response.reset();
            System.out.println("开始下载zip文件");
            //设置返回文件流格式
            response.setCharacterEncoding("UTF-8");
//            response.addHeader("Access-Control-Allow-Origin","*");
            response.addHeader("Access-Control-Allow-Methods","GET,POST,PUT,DELETE");
            response.addHeader("Access-Control-Allow-Headers","Content-Type");
            response.setHeader("content-Type", "application/octet-stream");
            response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(downloadName, "UTF-8"));
            out = response.getOutputStream();
            ZipUtil.dir2Zip(path, out, true);
            out.flush();
            out.close();
            System.out.println("下载zip文件成功");
        } catch (IOException ex) {
            ex.printStackTrace();
            System.out.println("下载zip文件失败" + ex.getMessage());
        } finally {
            try {
                out.flush();
                out.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public static void setContentType(String fileName, HttpServletResponse response){
        // 获取文件后缀
        String fileExtentName = fileName.substring(fileName.lastIndexOf(".") + 1);
        //设置返回文件流格式
        response.setCharacterEncoding("UTF-8");
        String ext = fileExtentName.toLowerCase();
        if (ext.equals("gif")){
            response.setHeader("content-Type", "image/gif");
        }else if (ext.equals("jfif") || ext.equals("jpe") || ext.equals("jpg") || ext.equals("jpeg")){
            response.setHeader("content-Type", "image/jpeg");
        }else if (ext.equals("ico")){
            response.setHeader("content-Type", "image/x-icon");
        }else if (ext.equals("png")){
            response.setHeader("content-Type", "image/png");
        }else if (ext.equals("tif") || ext.equals("tiff") ){
            response.setHeader("content-Type", "image/tiff");
        }else if (ext.equals("xsl") || ext.equals("xslt") || ext.equals("xsd") || ext.equals("xquery") || ext.equals("svg") || ext.equals("xql") || ext.equals("xml")){
            response.setHeader("content-Type", "text/xml");
        }else if (ext.equals("htm") || ext.equals("html") || ext.equals("htx") || ext.equals("jsp") || ext.equals("plg") || ext.equals("xhtml") ){
            response.setHeader("content-Type", "text/html");
        }else if (ext.equals("txt") ){
            response.setHeader("content-Type", "text/plain");
        }else if (ext.equals("pdf") ){
            response.setHeader("content-Type", "application/pdf");
        }else{
            response.setHeader("content-Type", "application/octet-stream");
        }
    }

    /**
    * @描述: 判断流的编码格式，不能百分之百判断正确
    * @入参:
    * @出参:
    * @作者: 代鹏刚
    * @日期: 2021/5/17 20:26
    **/
    public static Charset guessCharset(InputStream is) {
        try {
            return Charset.forName(new TikaEncodingDetector().guessEncoding(is));
        }catch (Exception e){
            e.printStackTrace();
        }
        return null;
    }

    public static ByteArrayOutputStream cloneInputStream(InputStream input) {
        try {
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            byte[] buffer = new byte[1024];
            int len;
            while ((len = input.read(buffer)) > -1) {
                baos.write(buffer, 0, len);
            }
            baos.flush();
            return baos;
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
    }
}
